#include "Lexico.h"

/* @resume:construtor */
Lexico::Lexico() {
	setLetra();
	setNumero();
	setPalavrasReservadas();
	setSimbolos();
}

/* @resume:destrutor */
Lexico::~Lexico() {

}

/* @resume:funcao para setar o vetor letra */
void Lexico::setLetra() {
	string letras = "abcdefghijklmnopqrstuvxywzABCDEFGHIJKLMNOPQRSTUVXYWZ_";
	for (unsigned i = 0; i < letras.length(); i++) {
		this->letra.push_back(letras[i]);
	}
}

/* @resume:funcao para setar o vetor numero */
void Lexico::setNumero() {
	string numero = "0123456789";
	for (unsigned i = 0; i < numero.length(); i++) {
		this->numero.push_back(numero[i]);
	}
}
/* @resume:funcao para setar o lista palavra reservada */
void Lexico::setPalavrasReservadas() {
	this->palavrasReservadas.push_front("and");
	this->palavrasReservadas.push_front("asm");
	this->palavrasReservadas.push_front("array");
	this->palavrasReservadas.push_front("begin");
	this->palavrasReservadas.push_front("case");
	this->palavrasReservadas.push_front("const");
	this->palavrasReservadas.push_front("constructor");
	this->palavrasReservadas.push_front("destructor");
	this->palavrasReservadas.push_front("div");
	this->palavrasReservadas.push_front("do");
	this->palavrasReservadas.push_front("downto");
	this->palavrasReservadas.push_front("else");
	this->palavrasReservadas.push_front("end");
	this->palavrasReservadas.push_front("exports");
	this->palavrasReservadas.push_front("file");
	this->palavrasReservadas.push_front("for");
	this->palavrasReservadas.push_front("function");
	this->palavrasReservadas.push_front("goto");
	this->palavrasReservadas.push_front("if");
	this->palavrasReservadas.push_front("implementation");
	this->palavrasReservadas.push_front("in");
	this->palavrasReservadas.push_front("inherited");
	this->palavrasReservadas.push_front("inline");
	this->palavrasReservadas.push_front("interface");
	this->palavrasReservadas.push_front("label");
	this->palavrasReservadas.push_front("library");
	this->palavrasReservadas.push_front("mod");
	this->palavrasReservadas.push_front("nil");
	this->palavrasReservadas.push_front("not");
	this->palavrasReservadas.push_front("object");
	this->palavrasReservadas.push_front("of");
	this->palavrasReservadas.push_front("or");
	this->palavrasReservadas.push_front("packed");
	this->palavrasReservadas.push_front("procedure");
	this->palavrasReservadas.push_front("program");
	this->palavrasReservadas.push_front("record");
	this->palavrasReservadas.push_front("repeat");
	this->palavrasReservadas.push_front("set");
	this->palavrasReservadas.push_front("shl");
	this->palavrasReservadas.push_front("shr");
	this->palavrasReservadas.push_front("string");
	this->palavrasReservadas.push_front("then");
	this->palavrasReservadas.push_front("to");
	this->palavrasReservadas.push_front("type");
	this->palavrasReservadas.push_front("until");
	this->palavrasReservadas.push_front("unit");
	this->palavrasReservadas.push_front("uses");
	this->palavrasReservadas.push_front("var");
	this->palavrasReservadas.push_front("while");
	this->palavrasReservadas.push_front("with");
	this->palavrasReservadas.push_front("xor");
}

/* @resume:funcao para setar o vetor simbolos */
void Lexico::setSimbolos() {
	string simbolos = "+-*=_:;.,<>(){}'^[]";
	for (unsigned i = 0; i < simbolos.length(); i++) {
		this->simbolos.push_back(simbolos[i]);
	}
}

/*
 * @resume:funcao para verificar se o caract eh inicio de comentario simples
 * @param: char caract
 * @return: true: se for incio de comentario
 *          false: se nao
 */
bool Lexico::isInicioDeComentario(char caract) {
	if (caract == '{')
		return true;
	return false;
}

/*
 * @resume:funcao para verificar se o caract eh delimitador simples
 * @param: char caract
 * @return: true: se for um delimitador simples
 *          false: se nao
 */
bool Lexico::isDelimitadorSimples(char caract) {
	if (caract == ':' || caract == ',' || caract == ';' || caract == '.'
			|| caract == 39 || caract == '(' || caract == ')')
		return true;
	return false;
}

/*
 * @resume:funcao para verificar se o caract eh inicio de comentario composto
 * @param: char caract1, char caract2
 * @return: true: se for incio de comentario composto
 *          false: se nao
 */
bool Lexico::isInicioDeComentario(char caract1, char caract2) {
	if (caract1 == '(' && caract2 == '*')
		return true;
	return false;
}

/*
 * @resume:funcao para verificar se o caract eh fim de comentario simples
 * @param: char caract
 * @return: true: se for fim de comentario simples
 *          false: se nao
 */
bool Lexico::isFimDeComentario(char caract) {
	if (caract == '}')
		return true;
	return false;
}

/*
 * @resume:funcao para verificar se o caract eh um espaco
 * @param: char caract
 * @return: true: se for espaco
 *          false: se nao
 */
bool Lexico::isEspaco(char caract) {
	if ((caract == ' ')||(caract == '\t')) {
		return true;
	}
	return false;

}

/*
 * @resume:funcao para verificar se o caract eh fim de comentario composto
 * @param: char caract1, char caract2
 * @return: true: se for fim de comentario composto
 *          false: se nao
 */
bool Lexico::isFimDeComentario(char caract1, char caract2) {
	if (caract1 == '*' && caract2 == ')')
		return true;
	return false;
}

/*
 * @resume:funcao para verificar se simbolo composto
 * @param: char caract1, char caract2
 * @return: true: se for simbolo composto
 *          false: se nao
 */
bool Lexico::isSimboloComposto(char caract1, char caract2) {
	if (caract1 == '<' && caract2 == '>')
		return true;
	else if (caract1 == '<' && caract2 == '=')
		return true;
	else if (caract1 == '>' && caract2 == '=')
		return true;
	else if (caract1 == ':' && caract2 == '=')
		return true;
	else
		return false;
}

/*
 * @resume:funcao para verificar se a palavra eh reservada
 * @param: string word
 * @return: true: se for palavra reservada
 *          false: se nao
 */
bool Lexico::isPalavraReservada(string word) {
	std::list<string>::iterator it;
	for (it = this->palavrasReservadas.begin() ; it
			!= this->palavrasReservadas.end(); it++)
		if (!stricmp(word.c_str(), (*it).c_str()))
			return true;
	return false;
}

/*
 * @resume:funcao para verificar se a palavra booleano
 * @param: string word
 * @return: true: se for palavra booleano
 *          false: se nao
 */
bool Lexico::isBooleano(string word) {
	if (word.compare("true") == 0)
		return true;
	else if (word.compare("false") == 0)
		return true;
	else
		return false;
}

/*
 * @resume:funcao para verificar se o caracter eh numero
 * @param: char caract
 * @return: true: se for o caracter for numero
 *          false: se nao
 */
bool Lexico::isNumero(char caract) {
	unsigned int i;
	vector<int>::size_type tamanho = this->numero.size();
	for (i = 0; i < tamanho; i++) {
		if (caract == this->numero[i]) {
			return true;
		}
	}
	return false;
}

/*
 * @resume:funcao para verificar se o caracter eh letra
 * @param: char caract
 * @return: true: se for o caracter for letra
 *          false: se nao
 */
bool Lexico::isLetra(char caract) {
	unsigned int i;
	vector<int>::size_type tamanho = this->letra.size();
	for (i = 0; i < tamanho; i++) {
		if (this->letra[i] == caract) {
			return true;
		}
	}
	return false;
}

/*
 * @resume:funcao para verificar se o caracter eh simbolo simples
 * @param: char caract
 * @return: true: se for o caracter for simbolo simples
 *          false: se nao
 */
bool Lexico::isSimboloSimples(char caract) {
	unsigned int i;
	vector<int>::size_type tamanho = this->simbolos.size();
	for (i = 0; i < tamanho; i++) {
		if (this->simbolos[i] == caract) {
			return true;
		}
	}
	return false;
}

/*
 * @resume:funcao para verificar setar o endereco dos arquivos de entrada e saida
 * @param: char* endEntrada, char* endSaida
 */
void Lexico::setArquivo(char* endEntrada, char* endSaida) {
	this->arquivoEntrada.setEnd(endEntrada);
	this->arquivoSaida.setEnd(endSaida);
}

/*
 * @resume:funcao para realizar a analise Lexico
 */
bool Lexico::analiseLexico() {
	/* abrindo o arquivo para realizar analise */
	this->arquivoEntrada.abrir(fstream::in);

	/*
	 * lista que ira conter somente os conjunto do caracter de cada linha do programa
	 * apos ser retirado os espacos
	 */
	list<string> brokeLine;

	/* variavel q recebe o elemento do vetor breakLine */
	string elementoAtual;

	/* variavel que rebe a linha do arquivo */
	string linhaAtual;

	/* variavel que ira conter o token */
	char buffer[255];

	/* variavel que contem a linha atual do arquivo */
	int numLinhaAtual = 0;

	/* variavel que sera o index, para o controle do buffer */
	int indice = 0;

	/* variaveis locais */
	bool comentarioSimplesFound = false;
	bool comentarioCompostoFound = false;
	bool letra = false;
	bool numero = false;
	bool identificador = false;
	bool error = false;
	bool inteiro = false;

	/* para verificar se houve erro na analise lexico */
	bool errorFound = false;

	/* ate que o fim do arquivo seja encontrado */
	while (!this->arquivoEntrada.isTheEnd()) {
		/* resetando as variaveis locais */
		indice = 0;
		identificador = false;
		numero = false;
		letra = false;
		error = false;
		inteiro = false;

		/* atualizando a linha atual */
		numLinhaAtual++;

		/* pegando a linha atual do arquivo */
		linhaAtual = this->arquivoEntrada.getLinha();

		/* -------------------------ESPACOS-------------------------- */

		/* tirando os espacos da string linhaAtual e colocando no vector */
		brokeLine = brokenString(linhaAtual);

		/* tamanho do vector */
		unsigned tamanho = brokeLine.size();

		/*verificando cada caracter da linhaAtual */
		for (unsigned j = 0; j < tamanho; j++) {
			elementoAtual = brokeLine.front();
			brokeLine.pop_front();
			indice = 0;
			/*verificando cada caracter do elementoAtual */
			for (unsigned i = 0; i < elementoAtual.length(); i++) {

				/* verifica cada caracter da string elementoAtual */
				switch (verificarCaracter(elementoAtual.at(i))) {

				/* ---------------------------------LETRAS-------------------------------- */
				case LETRA:

					/* verifica se o estado atual eh de comentario */
					if ((comentarioCompostoFound == true)
							|| (comentarioSimplesFound == true)) {
						break;
					}

					/* logo leu uma letra */
					letra = true;

					/* incrementa o buffer com cada caracter */
					buffer[indice++] = elementoAtual.at(i);

					/* se o primeiro caracter nao for numero entao pode ser um identificador */
					if (!numero) {
						identificador = true;
					}

					/* caso o identificador comeca com numero, entao seta o erro */
					else if (!identificador) {
						error = true;
						errorFound = true;
					}
					break;

					/* --------------------------------NUMEROS-------------------------------- */
				case NUMERO:

					/* verifica se o estado atual eh de comentario */
					if ((comentarioCompostoFound == true)
							|| (comentarioSimplesFound == true)) {
						break;
					}

					/* logo leu um numero */
					numero = true;

					/* coloca no buffer */
					buffer[indice++]= elementoAtual.at(i);

					/* se nenhuma letra foi lida entao eh somente numero */
					if (!letra) {
						inteiro = true;
					}
					break;

					/* ----------------------------------SIMBOLOS------------------------------ */

				case SIMBOLO:

					/* verifica se o estado atual eh de comentario */
					if ((comentarioCompostoFound == true)
							|| (comentarioSimplesFound == true)) {
						/*
						 * como fechou o comentario, tem q mover a cabeca do
						 * buffer para o inicio
						 */
						indice = 0;

						/* -------------------------FECHAMENTO COMENTARIO {----------------------- */

						/* verifica se eh comentario simples */
						if (comentarioSimplesFound) {
							/* verifica se o caracter atual eh um comentario simples */
							if (isFimDeComentario(elementoAtual.at(i))) {
								criarTokenizer("}", SIMBOLO_ESPECIAL,
										numLinhaAtual, "Fechamento Comentario");
								comentarioSimplesFound = false;
							}
						}

						/* -------------------------FECHAMENTO COMENTARIO *)---------------------- */

						/* verifica se o caracter atual eh um comentario composto */
						else if (i + 1 < elementoAtual.length()) {
							if (isFimDeComentario(elementoAtual.at(i),
									elementoAtual.at(i+1))) {
								comentarioCompostoFound = false;
								criarTokenizer("*)", SIMBOLO_COMPOSTO,
										numLinhaAtual, "Fechamento Comentario");
								/* como ja foi analizada elementoAtual.at(i+1) */
								i++;
							}
						}

					}

					/* nao esta no estado de comentario */
					else {
						/* caso exista algo antes de algum simbolo, tem q armazenar */
						if (indice> 0) {
							buffer[indice] = '\0';
							if (identificador) {
								if (isBooleano(buffer)) {
									criarTokenizer(buffer, BOOLEANO,
											numLinhaAtual, "");
								} else if (isPalavraReservada(buffer)) {
									criarTokenizer(buffer, RESERVADA,
											numLinhaAtual, "");
								} else {
									criarTokenizer(buffer, IDENTIFICADOR,
											numLinhaAtual, "");
								}
							}

							else if (inteiro) {
								if (error) {
									criarTokenizer(buffer, ERRO, numLinhaAtual,
											"Identificador Invalido");
								} else {
									criarTokenizer(buffer, INTEIRO,
											numLinhaAtual, "");
								}
							}
							if (error) {
								errorFound = true;
							}
						}
						/* -------------------------ABERTURA COMENTARIO {-------------------------- */

						/* verifica se o caracter atual eh um inicio de comentario simples */
						if (isInicioDeComentario(elementoAtual.at(i))) {
							criarTokenizer("{", SIMBOLO_ESPECIAL,
									numLinhaAtual, "Abertura Comentario");
							comentarioSimplesFound = true;
						}

						/* -------------------------ABERTURA COMENTARIO (*-------------------------- */

						/* verifica se o caracter atual eh um inicio de comentario composto */
						else if (i + 1 < elementoAtual.length()) {
							if (isInicioDeComentario(elementoAtual.at(i),
									elementoAtual.at(i+1))) {
								criarTokenizer("(*", SIMBOLO_COMPOSTO,
										numLinhaAtual, "Abertura Comentario");
								comentarioCompostoFound = true;
								//i++; // colocamos por teste
							}
						}

						/*
						 * nao sendo simbolos de comentarios,
						 * restam outros simbolos simples e composto
						 */
						if ((comentarioCompostoFound == false)
								&& (comentarioSimplesFound == false)) {
							bool simboloComposto = false;
							if (i + 1 < elementoAtual.length()) {

								/* -------------------------SIMBOLO COMPOSTO-------------------------- */

								/* caso for um comentario composto */
								if (isSimboloComposto(elementoAtual.at(i),
										elementoAtual.at(i+1))) {
									buffer[0]= elementoAtual.at(i);
									buffer[1]= elementoAtual.at(i+1);
									buffer[2]= '\0';
									simboloComposto = true;
									criarTokenizer(buffer, SIMBOLO_COMPOSTO,
											numLinhaAtual, "");
									i++;
								}
							}
							/* -------------------------SIMBOLO SIMPLES-------------------------- */

							/* caso for um comentario simples */
							if (simboloComposto == false) {
								buffer[0]= elementoAtual.at(i);
								buffer[1]= '\0';
								criarTokenizer(buffer, SIMBOLO_ESPECIAL,
										numLinhaAtual, "");
							}
						}
					}

					/* zerar as variaveis locais */
					indice = 0;
					numero = false;
					letra = false;
					inteiro = false;
					identificador = false;
					error = false;
					break;

					/* ---------------------------------DESCONHECIDOS----------------------------- */
				case DESCONHECIDO:

					/* verifica se o estado atual eh de comentario */
					if ((comentarioCompostoFound == true)
							|| (comentarioSimplesFound == true)) {
						break;
					}

					/* armazenar caracter desconhecido */
					string caracterDesconhecido;
					caracterDesconhecido += elementoAtual.at(i);

					/* caso chegue aki, eh pq houve um erro */
					errorFound = true;

					/*
					 * como o elementoAtual possui o caracter invalido,
					 * logo eh um indentificador invalido
					 */

					if (elementoAtual.length()> 0) {
						while (i < elementoAtual.length()) {
							buffer[indice] = elementoAtual.at(i);
							if (isDelimitadorSimples(elementoAtual.at(i))) {
								i--;
								break;
							}
							indice++;
							i++;

						}
						/* fecha o buffer */
						buffer[indice] = '\0';

						/* cria o token */
						if (indice> 1) {

							/* eh o identificador */
							criarTokenizer(buffer, ERRO, numLinhaAtual,
									"Identificador Invalido");
						} else {

							/* eh o caracter */
							criarTokenizer(caracterDesconhecido, ERRO,
									numLinhaAtual, "Caracter Desconhecido");
						}
					}

					/* zerar as variaveis locais */
					indice = 0;
					numero = false;
					letra = false;
					inteiro = false;
					identificador = false;
					error = false;
					break;
				}
			} /*for (unsigned i = 0; i < elementoAtual.length(); i++) */

			if ((comentarioSimplesFound == false) && (comentarioCompostoFound
					== false)) {
				if (indice> 0) {
					buffer[indice]= '\0';
					/*
					 * verifica se eh identificador, caso for verifica se eh booleano
					 * ou palavra reservada ou Identificador propriamente dito
					 */
					if (identificador) {
						if (isBooleano(buffer)) {
							criarTokenizer(buffer, BOOLEANO, numLinhaAtual, "");
						} else if (isPalavraReservada(buffer)) {
							criarTokenizer(buffer, RESERVADA, numLinhaAtual, "");
						} else {
							criarTokenizer(buffer, IDENTIFICADOR,
									numLinhaAtual, "");
						}

					}

					/*
					 * caso nao eh identificador eh um inteiro, verifica primeiro se existe erro
					 * e depois se for inteiro propriamente dito
					 */
					else if (inteiro) {
						if (error) {
							criarTokenizer(buffer, ERRO, numLinhaAtual,
									"Identificador Invalido");
							errorFound = true;
						} else {
							criarTokenizer(buffer, INTEIRO, numLinhaAtual, "");
						}
					}
				}
				/* zerar as variaveis locais */
				indice = 0;
				numero = false;
				letra = false;
				inteiro = false;
				identificador = false;
				error = false;
			}
		}/*for (unsigned j = 0; j < tamanho; j++) */

	}/* final do while */

	/* fechando o arquivoEntrada*/
	this->arquivoEntrada.fechar();
	return(!errorFound);

}

/*
 * @resume:funcao para verificar qual o tipo do caracter
 * @param: char caract
 * @return: LETRA: se o caracter for letra
 *          NUMERO: se for numero
 *          SIMBOLO: se for simbolo
 *          DESCONHECIDO: se nao for nem letra, nem numero, nem simbolo
 */
int Lexico::verificarCaracter(char caracter) {
	if (isLetra(caracter)) {
		return LETRA;
	} else if (isNumero(caracter)) {
		return NUMERO;
	} else if (isSimboloSimples(caracter)) {
		return SIMBOLO;
	} else {
		return DESCONHECIDO;
	}
}

/*
 * @resume:funcao para criar a lista de token encontrado na analise Lexico
 * @param: string buffer, tipoToken typeTokenizer,int numLinha, string comentario
 */
void Lexico::criarTokenizer(string buffer, tipoToken typeTokenizer,
		int numLinha, string comentario) {
	Tokenizer newToken(buffer, typeTokenizer, numLinha, comentario);
	this->tokensEncontrado.push_back(newToken);
}

/*
 * @resume:funcao para imprimir o resultado do analise lexico
 */
void Lexico::imprimirToken() {
	string aux = "";
	int numErro = 0;
	this->arquivoSaida.abrir(fstream::out);

	this->arquivoSaida.write("  ********************************************************************************** \n");
	this->arquivoSaida.write("  *Alunos:     Pedro Sanches Junior                                                * \n");
	this->arquivoSaida.write("  *            Thiago Augusto Lopes Genez                                          * \n");
	this->arquivoSaida.write("  *                                                                                * \n");
	this->arquivoSaida.write("  *Professora: Cintyan                                                             * \n");
	this->arquivoSaida.write("  *                                                                                * \n");
	this->arquivoSaida.write("  *Disciplina: Compiladores                                                        * \n");
	this->arquivoSaida.write("  ********************************************************************************** \n\n");

	this->arquivoSaida.write("  ---------------------------------------------------------------------------------- \n");
	this->arquivoSaida.write(" |                        A N A L I S A D O R   L E X I C O                         |\n");
	this->arquivoSaida.write(" |__________________________________________________________________________________|\n");
	this->arquivoSaida.write(" |        TOKEN         |   LINHA  |         TIPO         |        COMENTARIO       |\n");
	this->arquivoSaida.write(" |----------------------------------------------------------------------------------|\n");

	for (this->itToken = this->tokensEncontrado.begin(); this->itToken
			!= this->tokensEncontrado.end(); this->itToken++) {
		this->arquivoSaida.write(" |");
		this->arquivoSaida.write((*itToken).getConteudo());
		for (int i = (*itToken).getConteudo().length(); i < 22; i++) {
			this->arquivoSaida.write(" ");
		}
		aux = intToString((*itToken).getLinha());
		if (aux.length() == 1) {
			this->arquivoSaida.write("|    "+aux+"     |");
		} else if (aux.length() == 2) {
			this->arquivoSaida.write("|    "+aux+"    |");
		} else if (aux.length() == 3) {
			this->arquivoSaida.write("|    "+aux+"   |");
		}
		aux = (*itToken).tipoToString();
		if ((*itToken).tipoToString().compare("Erro") == 0) {
			numErro++;
		}
		for (int i = (*itToken).tipoToString().length(); i < 20; i++) {
			aux += " ";
		}
		this->arquivoSaida.write("  "+aux+"|");
		aux = (*itToken).getComentario();
		for (int i = (*itToken).getComentario().length(); i < 22; i++) {
			aux += " ";
		}
		this->arquivoSaida.write("  "+aux+" |");
		this->arquivoSaida.write("  ");
		this->arquivoSaida.write("\n");
	}
	aux = intToString(this->tokensEncontrado.size());
	this->arquivoSaida.write(" |__________________________________________________________________________________|\n");
	if (aux.length() == 1) {
		this->arquivoSaida.write(" |Total de Tokens encontrado = "+aux
				+"                                                    |\n");
	} else if (aux.length() == 2) {
		this->arquivoSaida.write(" |Total de Tokens encontrado = "+aux
				+"                                                   |\n");
	} else if (aux.length() == 3) {
		this->arquivoSaida.write(" |Total de Tokens encontrado = "+aux
				+"                                                  |\n");
	} else if (aux.length() == 4) {
		this->arquivoSaida.write(" |Total de Tokens encontrado = "+aux
				+"                                                 |\n");
	}

	if (intToString(numErro).length() == 1) {
		this->arquivoSaida.write(" |Numero de Erros: "+intToString(numErro)
				+"                                                                |\n");
	} else if (intToString(numErro).length() == 2) {
		this->arquivoSaida.write(" |Numero de Erros: "+intToString(numErro)
				+"                                                               |\n");
	} else if (intToString(numErro).length() == 3) {
		this->arquivoSaida.write(" |Numero de Erros: "+intToString(numErro)
				+"                                                              |\n");
	} else if (intToString(numErro).length() == 4) {
		this->arquivoSaida.write(" |Numero de Erros: "+intToString(numErro)
				+"                                                             |\n");
	}

	this->arquivoSaida.write("  ---------------------------------------------------------------------------------- \n");
	this->arquivoSaida.fechar();
}

/*
 * @resume:funcao para transformar int em String
 * @param: int num
 * @return: string num
 */
string Lexico::intToString(int num) {
	stringstream out;
	out << num;
	return (out.str());
}

/*
 * @resume:funcao para quebrar uma string retirando os espacos
 * @param: string line
 * @return: list<string>broken
 */
list<string> Lexico::brokenString(string line) {
	list<string> broken;
	char *cstr, *p;
	cstr = new char [line.size()+1];
	strcpy(cstr, line.c_str());
	p = strtok(cstr, " \t\n");
	while (p != NULL) {
		broken.push_back(p);
		p = strtok(NULL," \t\n");
	}
	delete[] cstr;
	return broken;
}

list<Tokenizer> Lexico::getTokensEncontrado(){
	return(this->tokensEncontrado);
}
